#ifndef TEMPLE_THIRD_MYSQL_H_
#define TEMPLE_THIRD_MYSQL_H_

#include "util/stdc.h"
#include <mysql/mysql.h>
#include <mysql/errmsg.h>

class Mysql
{
public:
    Mysql(const string &host, int port, const string &db, const string &user, const string &pass)
    {
        this->host = host;
        this->port = port;
        this->db = db;
        this->user = user;
        this->pass = pass;
		conn = nullptr;
        mysql_library_init(0, NULL, NULL);
    }

    Mysql(string &&host, int port, string &&db, string &&user, string &&pass)
    {
        this->host = host;
        this->port = port;
        this->db = db;
        this->user = user;
        this->pass = pass;
		conn = nullptr;
        mysql_library_init(0, NULL, NULL);
    }

    ~Mysql()
    {
        disconn();
        mysql_library_end();
    }

    int select_top(const string &sql, std::vector<string> &result)
	{
		if (!conn && connect())
        {
            return 1;
        }

		if (mysql_real_query(conn, sql.c_str(), sql.length()))
		{
			set_last_error();
    		return 2;
		}

		MYSQL_RES *res = mysql_store_result(conn);
		if (!res)
		{
			set_last_error();
    		return 3;
		}

		MYSQL_ROW row = mysql_fetch_row(res);
		if (!row)
		{
			set_last_error();
    		return 4;
		}

		u32 num = mysql_num_fields(res);
		result.assign(num, string());

		ulong *len = mysql_fetch_lengths(res);
		for (u32 i=0; i<num; ++i)
		{
			result[i].assign(row[i]?row[i]:"", len[i]);
		}

		mysql_free_result(res);
		return 0;
	}

    int select(const string &sql, std::vector<std::vector<string>> &results)
	{
		if (!conn && connect())
        {
            return 1;
        }

		if (mysql_real_query(conn, sql.c_str(), sql.length()))
		{
			set_last_error();
    		return 2;
		}

		MYSQL_RES *res = mysql_store_result(conn);
		if (!res)
		{
			set_last_error();
    		return 3;
		}

		MYSQL_ROW row = mysql_fetch_row(res);
		if (!row)
		{
			set_last_error();
    		return 4;
		}

		u32 row_num = (u32)mysql_num_rows(res);
		results.assign(row_num, std::vector<string>());

		for (u32 i=0; i<row_num; ++i)
		{
			u32 fld_num = mysql_num_fields(res);
			results[i].assign(fld_num, string());

			ulong *len = mysql_fetch_lengths(res);
			for (u32 j=0; j<fld_num; ++j)
			{
				results[i][j].assign(row[j]?row[j]:"", len[j]);
			}

			row = mysql_fetch_row(res);
			if (!row)
				break;
		}

		mysql_free_result(res);
		return 0;
	}

    int execute(const string &sql, u64 &affected)
	{
		if (!conn && connect())
        {
            return 1;
        }

		if (mysql_real_query(conn, sql.c_str(), sql.length()))
		{
			set_last_error();
    		return 2;
		}

		affected = mysql_affected_rows(conn);
		return 0;
	}

    int connect()
    {
        conn = mysql_init(NULL);

        if (!mysql_real_connect(conn, host.c_str(), user.c_str(), pass.c_str(), db.c_str(), port, NULL, 0))
        {
			set_last_error();
            mysql_close(conn);
            conn = NULL;
            return 1;
        }

		mysql_autocommit(conn, 1);
        return 0;
    }

    void disconn()
    {
        if (conn)
        {
            mysql_close(conn);
            conn = NULL;
        }
    }

    const char *get_last_error() const
    {
        return last_error;
    }

    void set_last_error()
    {
        last_error = mysql_error(conn);
        last_errno = mysql_errno(conn);

        if (last_errno == CR_SERVER_GONE_ERROR || last_errno == CR_SERVER_LOST)
        {
            disconn();
        }
    }

private:
    string host;
    int port;
    string db;
    string user;
    string pass;
    MYSQL *conn;

    const char *last_error;
    unsigned int last_errno;
};

#endif //TEMPLE_THIRD_MYSQL_H_